package be.darling.scrt.design;

import be.darling.scrt.data.Condition;
import be.darling.scrt.data.ObservedValues;
import be.darling.scrt.data.Randomization;
import be.darling.scrt.data.Value;
import be.darling.scrt.generator.Generator;
import be.darling.scrt.statistics.Statistic;
import be.darling.scrt.configuration.Configuration;
import be.darling.scrt.data.Reader;
import be.darling.scrt.utilities.InputFileProcessor;

import java.io.File;
import java.io.IOException;
import java.util.*;

import be.darling.scrt.utilities.XMLWriter;
import org.w3c.dom.Node;

/**
 * Created by IntelliJ IDEA.
 * User: geert
 * Date: Nov 20, 2007
 * Time: 8:44:47 PM
 * To change this template use File | Settings | File Templates.
 */
public class RandomizationTest {

    //TODO: ErrorHandling

    String name;
    String description; // + extra info

    //int n;

    Generator design; // + filters

    List<Statistic> statistics;
    List<Value> values;
    ObservedValues observed;
    // maxIterations?

    // run params:
    long totalIterations = 0;
    long validIterations = 0;
    Randomization currentIteration = null;
    volatile boolean running;

    long maxRandomIterations;
    long maxTotalIterations;

    long generationTime = 0;
    private String info;

    private String file;
    private String output;
    private File configurationFile;
    private Configuration configuration;

    private Reader reader;
    private Generator generator;

    private Date date;
    private List<String> authors = new ArrayList<String>();

    private HashMap<String, Object> options;
    private Node nodeOptions;

    public RandomizationTest(File configurationFile) {
        observed = new ObservedValues();
        statistics = new ArrayList();
        this.configuration = new Configuration(configurationFile);

        setOutput(configuration.getOutput());
    }

    public Condition getCondition(String labelName) {
        // case sensitive ??
        return design.getConditions().get(labelName.toLowerCase());
    }


    public synchronized void addObservation(String label, Value value) {
        addObservation(getCondition(label),value);
    }

    public synchronized void addObservation(Condition label, Value value) {
        observed.setValue(label, value);
    }

    public void addStatistic(Statistic stat) {
        statistics.add(stat);
    }

    public boolean isExhaustive() {
        // design.getExhaustive() ?
        return design.isComplete();
    }

    public Generator getDesign() {
        return design;
    }

    public void setDesign(Generator design) {
        this.design = design;
        Long maxTotal = design.getMaxTotalIterations();
        Long maxRandom = design.getMaxRandomIterations();

        maxTotalIterations = maxTotal !=null?maxTotal:configuration.getMaxTotalIterations();
        maxRandomIterations = maxRandom != null?maxRandom:configuration.getMaxRandomIterations();
    }


    public long getTotalIterations() {
        return totalIterations;
    }

    public long getValidIterations() {
        return validIterations;
    }

    public ObservedValues getObserved() {
        return observed;
    }

    public List<Statistic> getStatistics()
    {
        return statistics;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = (description==null?"":description);
    }

    public Reader getReader() {
        return reader;
    }

    public void setReader(Reader reader)
    {
        this.reader = reader;
    }

    public Generator getGenerator() {
        return generator;
    }

    public void setGenerator(Generator generator) {
        this.generator = generator;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public List<String> getAuthors() {
        return authors;
    }

    public HashMap<String, Object> getOptions() {
        return this.options;
    }

    public void setOptions(HashMap<String, Object> options) {
        this.options = options;

        setOutput( (String)options.get("output"));
    }

    public Node getNodeOptions() {
        return this.nodeOptions;
    }

    public void setNodeOptions(Node options) {
        this.nodeOptions = options;
    }

    public Map<String, Condition> getConditions() {
        return design.getConditions();
    }

    public void setRandomIterations(long iterations) {
            maxRandomIterations = iterations;
            maxTotalIterations = iterations; // * factor;
        //To change body of created methods use File | Settings | File Templates.
    }

    public File getConfigurationFile() {
        return configurationFile;
    }

    public void setConfigurationFile(File configurationFile) {
        this.configurationFile = configurationFile;
        this.configuration = new Configuration(configurationFile);
    }

    public String getFile() {
        return file;
    }

    public void setFile(String file) {
        this.file = file;
    }

    public String getOutput() {
        return output;
    }

    public void setOutput(String output) {
        this.output = output;
    }

    // XML Output write functions
    private void writeTestInfo(XMLWriter writer){
        writer.element("info",info);
        writer.element("design");

//        writer.element("class",design.getClass().getCanonicalName());
        //writer.element("param",design.getParam());
        writer.closeElement();
        //   <version>0.1</version>
        //   <timestamp></timestamp>
        writer.element("label");
        Collection labels = this.design.getConditions().values();
        for (Iterator iterator = labels.iterator(); iterator.hasNext();) {
            Condition label = (Condition) iterator.next();
            writer.element("label").attribute("id",label.getName());
            writer.write(label.getProperty("description"));
            writer.closeElement();
        }
        writer.closeElement();

        /*   <conditions>
              <label id="a">Baseline condition</label>
              <label id="b">Experimental cond ...</label>
           </conditions>

           <statistics>
              <statistic id="mean"><type>mean</type></statistic>
           </statistics>

           <observation>
                observation info
                 <obs id="1"><label>A</label><value param="x">0.5</value> </obs>
                 <obs id="2"><label>A</label><value param="x">0.5</value> </obs>
                 <obs id="3"><label>A</label><value param="x">0.5</value> </obs>
                 <obs id="4"><label>B</label><value param="x">0.5</value> </obs>
                 <obs id="5"><label>B</label><value param="x">0.5</value> </obs>
               more info
           </observation>

           <output>
            <name></name>
            <type></type>
              <params/>
           </output>
          */
    }

    private void writeRandomization(XMLWriter writer, Randomization rand){

    }

    private void writeResults(XMLWriter writer){

    }

    public void writeXML(File file) throws IOException {
        XMLWriter writer = new XMLWriter(file);
        writer.element("scrt");
        writeTestInfo(writer);

        writer.close();
        //System.out.println(writer.getResult());
        //out.flush();
        //out.close();
    }


    public synchronized void run() {

        readInputFile();

        processData();




        // checks
        //    * valid observations
        //    * valid observed randomization

        if(design.isDataValid(observed) )
        {

            // Main loop
            currentIteration = null;
            long startTime = System.currentTimeMillis();
            long startNano = System.nanoTime();
            totalIterations = 0;
            validIterations = 0;
            generationTime = 0;
            running = true;
            for (Iterator<Statistic> parameterIterator = statistics.iterator(); parameterIterator.hasNext();) {
                Statistic statistic = parameterIterator.next();
                statistic.setObservedValue(statistic.calc(observed));
                System.out.println("Observed: "+observed+"\t"+statistic.getObservedValue());
                statistic.setNanoTime(0);
                statistic.reset();
            }
            try{
                // todo later allow multiple threads in parallel
                startNano = System.nanoTime();
                while (((currentIteration = nextIteration()) != null ) && running) { // or check interrupted ??
                    generationTime += (System.nanoTime() - startNano);
                    //startNano = System.nanoTime();
                    totalIterations++;
                    if (isValid(currentIteration)){
                        validIterations++;
                        // post calculation to different worker thread.
                        for (Iterator<Statistic> parameterIterator = statistics.iterator(); parameterIterator.hasNext();) {
                            startNano = System.nanoTime();
                            Statistic statistic = parameterIterator.next();
                            statistic.setLastValue(statistic.calc(currentIteration));
                            statistic.addTime(System.nanoTime() - startNano);

                            //System.out.println("Iteration "+validIterations+" "+currentIteration+"\t"+statistic.getLastValue());
                        }

                        // check if output needed;
                        // output all iterations results
                        // output complete iteration details
                        //System.out.println("Iteration "+validIterations+" "+currentIteration);
                    }
                    //updateTimers  + notifyAll ??
                    Thread.yield();
                    if (!isExhaustive() && ((validIterations >= maxRandomIterations) || (totalIterations >= maxTotalIterations ))) {
                        // check running time ?
                        running = false;
                    }
                    startNano = System.nanoTime();
                }
            } catch (Exception e){
                e.printStackTrace();
                // report exception in output
            }
            System.out.println("profile "+(System.currentTimeMillis() - startTime));
            System.out.println("Generation Time "+ (generationTime / 1000000));
            for (Iterator<Statistic> parameterIterator = statistics.iterator(); parameterIterator.hasNext();) {
                Statistic statistic = parameterIterator.next();
                System.out.println("Stat "+statistic.p(false));
                System.out.println("Calc Time "+statistic.getNanoTime() / 1000000);
                //System.out.println("stat "+statistic.getLastValue());
            }

            // reporting and output

        }//end if design.isDataValid(observed)
    }

    private boolean isValid(Randomization currentIteration) {
        boolean valid = design.isValid(currentIteration);

        return valid;
    }

    private Randomization nextIteration() {
        List<Condition> next = null;
            List<Condition> labels = null;
            if (currentIteration != null){
                labels = new ArrayList<Condition>(currentIteration.getLabels());
            }
            next = design.generate(totalIterations,labels);
        if (next != null){  // or if exception caught ???
            return new Randomization(next,observed.getValues());
        }
        return null; // and stop iterating
    }


    public void setInfo(String info) {
        this.info = info;
    }

    public String getInfo() {
        return info;
    }

    public Iterator statisticsIterator()
    {
        return statistics.iterator();
    }

    private void readInputFile()
    {
        InputFileProcessor inputProcessor = new InputFileProcessor(this, configuration);
        inputProcessor.processInputFile();
    }

    private void processData()
    {
        reader.setRandomizationTest(this);
            //fills in the observed
        reader.read();

        design.setFirst(new ArrayList<Condition>(this.observed.getLabels() ) );
    }
    

}
